Skip to content

E2E tests for all workflow combinations#3456

Open
pepeladeira wants to merge 12 commits intomainfrom
feat/workflow-e2e-tests
Open

E2E tests for all workflow combinations#3456
pepeladeira wants to merge 12 commits intomainfrom
feat/workflow-e2e-tests

Conversation

@pepeladeira
Copy link
Collaborator

@pepeladeira pepeladeira commented Feb 11, 2026

Add E2E tests covering all three workflow types (AwardBounty, SendCampaign, MoveGroup) across both trigger mechanisms (partnerMetricsUpdated, partnerEnrolled).

Summary by CodeRabbit

  • Tests
    • Added comprehensive end-to-end test suites for workflow functionality, including bounty awards, group movements, and campaign sending.
    • Introduced test utilities for verifying workflow execution, commission tracking, and partner state changes.
    • Enhanced test infrastructure with dedicated database configuration and polling helpers for asynchronous workflow validation.

@vercel
Copy link
Contributor

vercel bot commented Feb 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dub Ready Ready Preview Feb 13, 2026 9:18pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

This PR adds comprehensive end-to-end test suites for three workflows (AwardBounty, MoveGroup, SendCampaign) with dedicated verification utilities and lead-tracking helpers. It also refactors the verifyCommission utility from HTTP-based queries to Prisma ORM and removes HTTP parameter dependencies from existing tests while introducing E2E-specific test infrastructure.

Changes

Cohort / File(s) Summary
Workflow Test Suites
apps/web/tests/workflows/award-bounty-workflow.test.ts, apps/web/tests/workflows/move-group-workflow.test.ts, apps/web/tests/workflows/send-campaign-workflow.test.ts
Adds comprehensive end-to-end tests for workflow execution across multiple scenarios including goal validation, disabled workflows, duplicate prevention, rule conditions, and enrollment-based eligibility checks.
Workflow Verification Utilities
apps/web/tests/workflows/utils/verify-bounty-submission.ts, apps/web/tests/workflows/utils/verify-campaign-sent.ts, apps/web/tests/workflows/utils/verify-partner-group-move.ts, apps/web/tests/workflows/utils/track-leads.ts
Introduces polling-based verification helpers for bounty submissions, campaign sends, and partner group movements; adds trackLeads utility to simulate click/lead events with HTTP interactions.
Test Infrastructure
apps/web/tests/utils/env.ts, apps/web/tests/utils/prisma.ts
Adds E2E_DATABASE_URL environment variable validation and creates a dedicated Prisma client for end-to-end tests with datasourceUrl configuration.
Verify Commission Refactoring
apps/web/tests/utils/verify-commission.ts
Refactors from HTTP-based customer and commission lookups to Prisma ORM queries; removes http parameter from function signature and updates return type to commission object.
Test Callers Updated
apps/web/tests/rewards/lead-reward.test.ts, apps/web/tests/rewards/sale-reward.test.ts, apps/web/tests/tracks/track-sale.test.ts
Removes http parameter from verifyCommission function calls following utility refactoring to use Prisma instead of HTTP endpoints.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • dubinc/dub#3020: Modifies the same verify-commission.ts test utility that is being refactored here from HTTP to Prisma-based queries.
  • dubinc/dub#2975: Adds end-to-end tests around campaign functionality using similar test infrastructure and IntegrationHarness patterns.
  • dubinc/dub#2562: Targets the same /track/click and /track/lead endpoints that the new trackLeads test utility exercises.

Suggested reviewers

  • devkiran
  • steven-tey

Poem

🐰 Hop, click, and test we must,
Workflows verified with Prisma's trust,
Bounties, campaigns, and groups on the move,
E2E suites put all code in groove!

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'E2E tests for all workflow combinations' clearly summarizes the main change: adding comprehensive end-to-end tests across all three workflow types (AwardBounty, SendCampaign, MoveGroup) with multiple trigger scenarios.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/workflow-e2e-tests

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/web/tests/utils/verify-commission.ts (1)

43-49: findFirst without orderBy may match a stale commission from a previous test run.

If test data isn't perfectly cleaned up between runs, findFirst with no ordering could return an older commission that happens to match the programId/customerId/invoiceId filters. Adding orderBy: { createdAt: "desc" } ensures the most recently created commission is checked first, reducing flaky-test risk.

Suggested fix
     const commission = await prisma.commission.findFirst({
       where: {
         programId: E2E_PROGRAM.id,
         ...(customerId && { customerId }),
         ...(invoiceId && { invoiceId }),
       },
+      orderBy: { createdAt: "desc" },
     });
apps/web/tests/workflows/utils/track-leads.ts (1)

4-35: Overall structure looks good — clean, reusable helper.

One minor note: customerEmail on Line 28 uses a fixed pattern (customer${i}@example.com) without any randomness, unlike customerExternalId and eventName which incorporate Date.now(). If any uniqueness constraint exists on email within the lead tracking pipeline, parallel or repeated test runs could collide on the same email addresses.

Consider using the same Date.now() suffix or randomEmail() (already available in ../utils/helpers) for customerEmail as well.

apps/web/tests/workflows/move-group-workflow.test.ts (2)

201-212: Partners created during tests are never cleaned up.

Tests 3–6 each create a partner via POST /partners but register no onTestFinished hook to delete them. Over repeated runs, orphaned partners will accumulate in the E2E database. Consider adding cleanup (e.g., via a partner delete endpoint or a Prisma deletion in onTestFinished).

Also applies to: 278-289, 355-367, 442-454


414-418: Nit: hardcoded slug duplicates the slug variable.

Line 416 uses the string literal "e2e-target-no-dup" instead of the slug variable declared on Line 400. All other tests consistently use the variable. Use slug here for consistency.

Suggested fix
       body: {
         name: "E2E Target Group - No Dup Move",
-        slug: "e2e-target-no-dup",
+        slug,
         color: randomValue(RESOURCE_COLORS),
       },
apps/web/tests/workflows/award-bounty-workflow.test.ts (2)

58-67: Redundant assertions after verifyBountySubmission.

verifyBountySubmission (from verify-bounty-submission.ts, lines 40-49 in the snippet) already asserts status === "submitted", performanceCount >= minPerformanceCount, and completedAt is not null. The assertions on lines 65-67 repeat the same checks on the returned object.

Not harmful, but removing them would reduce noise.


40-48: Partners created during tests are never cleaned up.

Same issue as in move-group-workflow.test.ts — each test creates a partner via POST /partners without registering cleanup. Consider adding onTestFinished hooks to delete them.

Also applies to: 96-104, 165-173, 220-228


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/web/tests/workflows/send-campaign-workflow.test.ts`:
- Around line 274-349: The test "Cron processes eligible partner enrollment"
incorrectly sets the partner enrollment time to 18 hours ago
(prisma.programEnrollment.update with subHours) while the campaign trigger
expects partnerEnrolledDays >= 1, and it never asserts that an email was sent;
update the test to set a createdAt >24 hours ago (e.g., use subHours(..., 25) or
subDays(..., 2)) so the triggerCondition will be met, then add a post-cron
verification call such as verifyCampaignSent({ campaignId, partnerId: partner.id
}) after calling callCronWorkflow to assert the campaign email was actually
sent; ensure you reference the workflow from prisma.workflow.findFirst and keep
the existing cleanup onTestFinished.
- Around line 488-498: The test sets the enrollment time with
prisma.programEnrollment.update to subHours(new Date(), 18), which may be less
than the required partnerEnrolledDays >= 1 and cause the cron to skip sending
(making the dedup assertion vacuous); update the test to set the enrollment
earlier (e.g., subDays(new Date(), 1) or subHours(new Date(), 25)) so the
partner meets the 1-day threshold before pre-creating the notificationEmail and
running the dedup scenario involving E2E_PROGRAM and the existing
notificationEmail record.
🧹 Nitpick comments (6)
apps/web/tests/workflows/utils/verify-campaign-sent.ts (1)

27-32: Assertions are tautological given the query's where clause.

The findFirst query already filters by campaignId, type: "Campaign", and partnerId, so asserting those same fields on the result will always pass. The only meaningful check is that the record exists (which the if (emailSent) guard already covers). Consider removing lines 28–31 to reduce noise, or keep them intentionally as documentation of the expected shape.

Simplified return
     if (emailSent) {
-      expect(emailSent).toBeDefined();
-      expect(emailSent.type).toBe("Campaign");
-      expect(emailSent.campaignId).toBe(campaignId);
-      expect(emailSent.partnerId).toBe(partnerId);
       return emailSent;
     }
apps/web/tests/workflows/utils/verify-bounty-submission.ts (1)

22-22: Consider typing lastSubmission instead of any.

Using the Prisma-generated type (e.g., BountySubmission | null) would give you compile-time safety on the error-message field access (line 59: lastSubmission.status, lastSubmission.performanceCount).

Type-safe alternative
+import { BountySubmission } from "@dub/prisma/client";
 ...
-  let lastSubmission: any = null;
+  let lastSubmission: BountySubmission | null = null;
apps/web/tests/workflows/award-bounty-workflow.test.ts (2)

10-42: trackLeads is duplicated verbatim in move-group-workflow.test.ts.

This helper appears identically in both award-bounty-workflow.test.ts (here) and move-group-workflow.test.ts (lines 12–44). Extract it to a shared utility under tests/workflows/utils/ to keep both files DRY and ensure future changes (e.g., adding delays, changing the tracking payload) are applied consistently.

Extract to shared utility

Create apps/web/tests/workflows/utils/track-leads.ts:

import { expect } from "vitest";
import { E2E_TRACK_CLICK_HEADERS } from "../../utils/resource";

export async function trackLeads(
  http: any,
  partnerLink: { domain: string; key: string },
  count: number,
) {
  for (let i = 0; i < count; i++) {
    const { status: clickStatus, data: clickData } = await http.post({
      path: "/track/click",
      headers: E2E_TRACK_CLICK_HEADERS,
      body: {
        domain: partnerLink.domain,
        key: partnerLink.key,
      },
    });

    expect(clickStatus).toEqual(200);
    expect(clickData.clickId).toBeDefined();

    const { status: leadStatus } = await http.post({
      path: "/track/lead",
      body: {
        clickId: clickData.clickId,
        eventName: `Signup-${i}-${Date.now()}`,
        customerExternalId: `e2e-customer-${i}-${Date.now()}`,
        customerEmail: `customer${i}@example.com`,
      },
    });

    expect(leadStatus).toEqual(200);

    await new Promise((resolve) => setTimeout(resolve, 200));
  }
}

104-160: Hardcoded 10-second sleep is fragile.

Line 147 uses a fixed 10-second wait to give the workflow time to not execute. If the system is slow, the workflow might still be processing, causing a false pass (draft found before workflow completes). If it's fast, 10 seconds is wasted CI time. Consider polling briefly (e.g., 2–3 iterations) and asserting draft status holds, or document why 10 seconds is a safe upper bound.

apps/web/tests/workflows/move-group-workflow.test.ts (2)

170-176: sourceGroup selection via existingGroups[0] is order-dependent and fragile.

Multiple tests (lines 176, 262, 336, 420) grab existingGroups[0] as the source group. The order of results from GET /groups may not be deterministic, and if another test creates a group before this one runs (even with sequential execution), the first element could change. Consider filtering by the known default group ID or slug instead:

Pin to the known default group
-    const sourceGroup = existingGroups[0];
+    const sourceGroup = existingGroups.find(g => g.slug === "default");
+    expect(sourceGroup).toBeDefined();

492-540: AND-operator test validates configuration but not execution.

This test verifies the workflow's triggerConditions are stored correctly with two rules, but doesn't test that both conditions must be satisfied for the move to occur (i.e., no execution test with one condition met and one not). Consider adding a follow-up test that exercises the AND semantics end-to-end.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web/tests/workflows/move-group-workflow.test.ts`:
- Around line 394-399: The test currently silently returns when enrollment is
falsy which masks E2E seed/configuration failures; replace the early-return
block that checks enrollment (the conditional using enrollment, partner.id and
programId) with an explicit assertion such as expect(enrollment).not.toBeNull()
(or expect(enrollment).toBeDefined()) so the test fails loudly if enrollment is
missing, or if the intent is truly to skip, convert the test to use test.skip
with a clear reason referencing the missing enrollment.
🧹 Nitpick comments (2)
apps/web/tests/workflows/utils/verify-bounty-submission.ts (1)

34-52: Redundant assertions inside the already-matched condition block.

Lines 40–46 re-assert conditions that were already verified by the if guard on lines 34–39. These expect calls will always pass at this point. They add noise without catching new failures. Consider removing them and keeping only the completedAt assertion (line 48–50), which checks something not in the guard.

♻️ Suggested simplification
     if (
       submission &&
       submission.status === expectedStatus &&
       (minPerformanceCount === undefined ||
         (submission.performanceCount ?? 0) >= minPerformanceCount)
     ) {
-      expect(submission.status).toBe(expectedStatus);
-
-      if (minPerformanceCount !== undefined) {
-        expect(submission.performanceCount).toBeGreaterThanOrEqual(
-          minPerformanceCount,
-        );
-      }
-
       if (expectedStatus === "submitted") {
         expect(submission.completedAt).not.toBeNull();
       }

       return submission;
     }
apps/web/tests/workflows/move-group-workflow.test.ts (1)

12-13: Consider typing the http parameter.

http: any loses type safety. If the IntegrationHarness exposes a type for its HTTP client, using it here would catch mistakes at compile time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant